Unified · Split
| @@ -76,7 +76,7 @@ impl Watcher for FsWatcher { · 75 unmodified lines | ||
| 76 | 76 | paths.get(ancestor).is_some_and(|entry| match entry { |
| 77 | 77 | FsWatcherRegistration::Pending { .. } => false, |
| 78 | 78 | FsWatcherRegistration::Registered { mode, .. } => { |
| 79 | − recursive_mode(*mode) == notify::RecursiveMode::Recursive | |
| 79 | + platform_recursive_mode(*mode) == notify::RecursiveMode::Recursive | |
| 80 | 80 | } |
| 81 | 81 | }) |
| 82 | 82 | }); |
| @@ -551,7 +551,7 @@ impl<T: notify::Watcher + Send> WatchBackend for T { · 468 unmodified lines | ||
| 551 | 551 | } |
| 552 | 552 | } |
| 553 | 553 | |
| 554 | −/// `GlobalWatcher` is the low-level manager of filesystem watching. There is one per `RealFs`. | |
| 554 | +/// `GlobalWatcher` is the low-level platform-agnostic manager of filesystem watching. There is one per `RealFs`. | |
| 555 | 555 | /// |
| 556 | 556 | /// `GlobalWatcher` talks to a `WatchBackend`, which handles the OS-specific details of filesystem watching. |
| 557 | 557 | /// In production, we use the `notify` crate for this. The job of `GlobalWatcher` is to convey watch and unwatch |
| @@ -648,13 +648,17 @@ impl GlobalWatcher { · 90 unmodified lines | ||
| 648 | 648 | } |
| 649 | 649 | |
| 650 | 650 | pub(crate) fn new(executor: &BackgroundExecutor) -> Self { |
| 651 | − Self::with_backends(executor, None, None) | |
| 651 | + Self::with_backends( | |
| 652 | + executor, | |
| 653 | + (None, platform_recursive_mode(WatcherMode::Native)), | |
| 654 | + (None, platform_recursive_mode(WatcherMode::Poll)), | |
| 655 | + ) | |
| 652 | 656 | } |
| 653 | 657 | |
| 654 | 658 | fn with_backends( |
| 655 | 659 | executor: &BackgroundExecutor, |
| 656 | − native_backend: Option<Box<dyn WatchBackend>>, | |
| 657 | − poll_backend: Option<Box<dyn WatchBackend>>, | |
| 660 | + native: (Option<Box<dyn WatchBackend>>, notify::RecursiveMode), | |
| 661 | + poll: (Option<Box<dyn WatchBackend>>, notify::RecursiveMode), | |
| 658 | 662 | ) -> Self { |
| 659 | 663 | let state = Arc::new(Mutex::new(WatcherState { |
| 660 | 664 | watchers: HashMap::new(), |
| @@ -672,10 +676,11 @@ impl GlobalWatcher { · 11 unmodified lines | ||
| 672 | 676 | executor: executor.clone(), |
| 673 | 677 | native: BackendState::new( |
| 674 | 678 | WatcherMode::Native, |
| 679 | + native.1, | |
| 675 | 680 | executor.clone(), |
| 676 | − native_backend, | |
| 681 | + native.0, | |
| 677 | 682 | ), |
| 678 | − poll: BackendState::new(WatcherMode::Poll, executor.clone(), poll_backend), | |
| 683 | + poll: BackendState::new(WatcherMode::Poll, poll.1, executor.clone(), poll.0), | |
| 679 | 684 | } |
| 680 | 685 | .run(wake_rx), |
| 681 | 686 | ) |
| @@ -690,6 +695,10 @@ impl GlobalWatcher { · 8 unmodified lines | ||
| 690 | 695 | |
| 691 | 696 | struct BackendState { |
| 692 | 697 | mode: WatcherMode, |
| 698 | + // Whether this backend watches recursively. Determined once at construction | |
| 699 | + // (from the platform for the real backend, or chosen explicitly in tests) so | |
| 700 | + // that coverage reasoning works even before the backend has been created. | |
| 701 | + recursive_mode: notify::RecursiveMode, | |
| 693 | 702 | executor: BackgroundExecutor, |
| 694 | 703 | backend: Option<Box<dyn WatchBackend>>, |
| 695 | 704 | /// Paths that we have successfully asked the backend to watch. |
| @@ -753,11 +762,11 @@ impl Reconciler { · 57 unmodified lines | ||
| 753 | 762 | health_checks = std::mem::take(&mut state.pending_health_checks); |
| 754 | 763 | let dirty = std::mem::take(&mut state.dirty_paths); |
| 755 | 764 | for (mode, path) in dirty { |
| 756 | − let affected_paths = match mode { | |
| 757 | − WatcherMode::Native => &mut native_affected_paths, | |
| 758 | − WatcherMode::Poll => &mut poll_affected_paths, | |
| 765 | + let (affected_paths, is_recursive) = match mode { | |
| 766 | + WatcherMode::Native => (&mut native_affected_paths, self.native.is_recursive()), | |
| 767 | + WatcherMode::Poll => (&mut poll_affected_paths, self.poll.is_recursive()), | |
| 759 | 768 | }; |
| 760 | − if recursive_mode(mode) == notify::RecursiveMode::Recursive { | |
| 769 | + if is_recursive { | |
| 761 | 770 | for (descendant, _) in state.path_registrations(mode).range::<Path, _>(( |
| 762 | 771 | std::ops::Bound::Excluded(path.as_ref()), |
| 763 | 772 | std::ops::Bound::Unbounded, |
| @@ -797,11 +806,13 @@ impl Reconciler { · 33 unmodified lines | ||
| 797 | 806 | impl BackendState { |
| 798 | 807 | fn new( |
| 799 | 808 | mode: WatcherMode, |
| 809 | + recursive_mode: notify::RecursiveMode, | |
| 800 | 810 | executor: BackgroundExecutor, |
| 801 | 811 | backend: Option<Box<dyn WatchBackend>>, |
| 802 | 812 | ) -> Self { |
| 803 | 813 | Self { |
| 804 | 814 | mode, |
| 815 | + recursive_mode, | |
| 805 | 816 | executor, |
| 806 | 817 | backend, |
| 807 | 818 | applied_paths: HashSet::new(), |
| @@ -811,6 +822,10 @@ impl BackendState { · 3 unmodified lines | ||
| 811 | 822 | } |
| 812 | 823 | } |
| 813 | 824 | |
| 825 | + fn is_recursive(&self) -> bool { | |
| 826 | + self.recursive_mode == notify::RecursiveMode::Recursive | |
| 827 | + } | |
| 828 | + | |
| 814 | 829 | fn reconcile( |
| 815 | 830 | &mut self, |
| 816 | 831 | affected_paths: HashSet<Arc<Path>>, |
| @@ -829,7 +844,7 @@ impl BackendState { · 12 unmodified lines | ||
| 829 | 844 | let registrations = watcher_state.path_registrations(self.mode); |
| 830 | 845 | for path in affected_paths { |
| 831 | 846 | let is_desired = registrations.contains_key(&path); |
| 832 | − let is_covered = recursive_mode(self.mode) == notify::RecursiveMode::Recursive | |
| 847 | + let is_covered = self.is_recursive() | |
| 833 | 848 | && path |
| 834 | 849 | .ancestors() |
| 835 | 850 | .skip(1) |
| @@ -938,7 +953,7 @@ impl BackendState { · 102 unmodified lines | ||
| 938 | 953 | |
| 939 | 954 | // Check that no path in the applied set is a strict child of another, |
| 940 | 955 | // if the backend is recursive, since this would be redundant. |
| 941 | − if recursive_mode(self.mode) == notify::RecursiveMode::Recursive { | |
| 956 | + if self.is_recursive() { | |
| 942 | 957 | let mut applied_paths = self |
| 943 | 958 | .applied_paths |
| 944 | 959 | .iter() |
| @@ -1017,7 +1032,7 @@ impl BackendState { · 72 unmodified lines | ||
| 1017 | 1032 | paths: Vec<Arc<Path>>, |
| 1018 | 1033 | watcher_state: &Arc<Mutex<WatcherState>>, |
| 1019 | 1034 | ) -> Option<Instant> { |
| 1020 | − let recursive_mode = recursive_mode(self.mode); | |
| 1035 | + let recursive_mode = self.recursive_mode; | |
| 1021 | 1036 | let mut queue = paths; |
| 1022 | 1037 | let mut wake_at = None; |
| 1023 | 1038 | while !queue.is_empty() { |
| @@ -1195,7 +1210,7 @@ fn create_backend( · 171 unmodified lines | ||
| 1195 | 1210 | } |
| 1196 | 1211 | } |
| 1197 | 1212 | |
| 1198 | −fn recursive_mode(mode: WatcherMode) -> notify::RecursiveMode { | |
| 1213 | +fn platform_recursive_mode(mode: WatcherMode) -> notify::RecursiveMode { | |
| 1199 | 1214 | match mode { |
| 1200 | 1215 | WatcherMode::Native => { |
| 1201 | 1216 | if cfg!(any(target_os = "windows", target_os = "macos")) { |
| @@ -1274,6 +1289,7 @@ fn handle_event(mode: WatcherMode, state: &Mutex<WatcherState>, event: notify::R · 72 unmodified lines | ||
| 1274 | 1289 | #[cfg(test)] |
| 1275 | 1290 | mod tests { |
| 1276 | 1291 | use super::*; |
| 1292 | + use notify::RecursiveMode; | |
| 1277 | 1293 | use std::{collections::HashSet, path::PathBuf}; |
| 1278 | 1294 | |
| 1279 | 1295 | fn rescan(path: &str) -> PathEvent { |
| @@ -1461,17 +1477,30 @@ mod tests { · 181 unmodified lines | ||
| 1461 | 1477 | } |
| 1462 | 1478 | } |
| 1463 | 1479 | |
| 1480 | + type FakeBackend = Option<(Arc<Mutex<FakeWatchBackend>>, notify::RecursiveMode)>; | |
| 1481 | + | |
| 1482 | + fn fake_backend( | |
| 1483 | + backend: FakeBackend, | |
| 1484 | + mode: WatcherMode, | |
| 1485 | + ) -> (Option<Box<dyn WatchBackend>>, notify::RecursiveMode) { | |
| 1486 | + match backend { | |
| 1487 | + Some((backend, recursive_mode)) => ( | |
| 1488 | + Some(Box::new(SharedFakeWatchBackend(backend)) as Box<dyn WatchBackend>), | |
| 1489 | + recursive_mode, | |
| 1490 | + ), | |
| 1491 | + None => (None, platform_recursive_mode(mode)), | |
| 1492 | + } | |
| 1493 | + } | |
| 1494 | + | |
| 1464 | 1495 | fn test_global( |
| 1465 | 1496 | executor: &BackgroundExecutor, |
| 1466 | − native_backend: Option<Arc<Mutex<FakeWatchBackend>>>, | |
| 1467 | − poll_backend: Option<Arc<Mutex<FakeWatchBackend>>>, | |
| 1497 | + native_backend: FakeBackend, | |
| 1498 | + poll_backend: FakeBackend, | |
| 1468 | 1499 | ) -> GlobalWatcher { |
| 1469 | 1500 | GlobalWatcher::with_backends( |
| 1470 | 1501 | executor, |
| 1471 | − native_backend | |
| 1472 | − .map(|backend| Box::new(SharedFakeWatchBackend(backend)) as Box<dyn WatchBackend>), | |
| 1473 | − poll_backend | |
| 1474 | − .map(|backend| Box::new(SharedFakeWatchBackend(backend)) as Box<dyn WatchBackend>), | |
| 1502 | + fake_backend(native_backend, WatcherMode::Native), | |
| 1503 | + fake_backend(poll_backend, WatcherMode::Poll), | |
| 1475 | 1504 | ) |
| 1476 | 1505 | } |
| 1477 | 1506 | |
| @@ -1495,7 +1524,14 @@ mod tests { · 17 unmodified lines | ||
| 1495 | 1524 | #[gpui::test] |
| 1496 | 1525 | async fn watch_and_unwatch_call_the_backend_once_per_path(executor: BackgroundExecutor) { |
| 1497 | 1526 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1498 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 1527 | + let global = test_global( | |
| 1528 | + &executor, | |
| 1529 | + Some(( | |
| 1530 | + backend.clone(), | |
| 1531 | + platform_recursive_mode(WatcherMode::Native), | |
| 1532 | + )), | |
| 1533 | + None, | |
| 1534 | + ); | |
| 1499 | 1535 | let path = Arc::<Path>::from(Path::new("/repo")); |
| 1500 | 1536 | |
| 1501 | 1537 | let first = global.add(path.clone(), WatcherMode::Native, noop_callback()); |
| @@ -1516,7 +1552,11 @@ mod tests { · 14 unmodified lines | ||
| 1516 | 1552 | #[gpui::test] |
| 1517 | 1553 | async fn covered_child_is_promoted_when_parent_is_unregistered(executor: BackgroundExecutor) { |
| 1518 | 1554 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1519 | − let global = test_global(&executor, None, Some(backend.clone())); | |
| 1555 | + let global = test_global( | |
| 1556 | + &executor, | |
| 1557 | + None, | |
| 1558 | + Some((backend.clone(), RecursiveMode::Recursive)), | |
| 1559 | + ); | |
| 1520 | 1560 | let parent = Arc::<Path>::from(Path::new("/repo")); |
| 1521 | 1561 | let child = Arc::<Path>::from(Path::new("/repo/foo.csproj")); |
| 1522 | 1562 | |
| @@ -1543,7 +1583,11 @@ mod tests { · 20 unmodified lines | ||
| 1543 | 1583 | #[gpui::test] |
| 1544 | 1584 | async fn child_is_demoted_when_covering_parent_is_registered(executor: BackgroundExecutor) { |
| 1545 | 1585 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1546 | − let global = test_global(&executor, None, Some(backend.clone())); | |
| 1586 | + let global = test_global( | |
| 1587 | + &executor, | |
| 1588 | + None, | |
| 1589 | + Some((backend.clone(), RecursiveMode::Recursive)), | |
| 1590 | + ); | |
| 1547 | 1591 | let parent = Arc::<Path>::from(Path::new("/repo")); |
| 1548 | 1592 | let child = Arc::<Path>::from(Path::new("/repo/sub")); |
| 1549 | 1593 | |
| @@ -1568,10 +1612,83 @@ mod tests { · 18 unmodified lines | ||
| 1568 | 1612 | assert!(!backend.watched_paths.contains(child.as_ref())); |
| 1569 | 1613 | } |
| 1570 | 1614 | |
| 1615 | + #[gpui::test] | |
| 1616 | + async fn non_recursive_backend_does_not_cover_children(executor: BackgroundExecutor) { | |
| 1617 | + // A non-recursive backend (e.g. inotify) does not deliver events for a | |
| 1618 | + // child via an ancestor's watch, so coverage must not kick in: every | |
| 1619 | + // registered path keeps its own watch, nothing is demoted, and removing an | |
| 1620 | + // ancestor leaves the descendant's watch untouched. This is the mirror | |
| 1621 | + // image of `covered_child_is_promoted_when_parent_is_unregistered`, where | |
| 1622 | + // a recursive ancestor covers the child and its removal promotes it. The | |
| 1623 | + // polymorphic fake lets us pin this on any host, independent of what | |
| 1624 | + // `platform_recursive_mode` would pick. | |
| 1625 | + let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); | |
| 1626 | + let global = test_global( | |
| 1627 | + &executor, | |
| 1628 | + Some((backend.clone(), RecursiveMode::NonRecursive)), | |
| 1629 | + None, | |
| 1630 | + ); | |
| 1631 | + let parent = Arc::<Path>::from(Path::new("/repo")); | |
| 1632 | + let child = Arc::<Path>::from(Path::new("/repo/sub")); | |
| 1633 | + | |
| 1634 | + let (child_events, child_callback) = collecting_callback(); | |
| 1635 | + global.add(child.clone(), WatcherMode::Native, child_callback); | |
| 1636 | + executor.run_until_parked(); | |
| 1637 | + assert_eq!(backend.lock().watch_calls, &[child.to_path_buf()]); | |
| 1638 | + | |
| 1639 | + let parent_id = global.add(parent.clone(), WatcherMode::Native, noop_callback()); | |
| 1640 | + executor.run_until_parked(); | |
| 1641 | + { | |
| 1642 | + let backend = backend.lock(); | |
| 1643 | + assert_eq!( | |
| 1644 | + backend.watch_calls, | |
| 1645 | + &[child.to_path_buf(), parent.to_path_buf()], | |
| 1646 | + "the parent gets its own watch without covering the child" | |
| 1647 | + ); | |
| 1648 | + assert_eq!( | |
| 1649 | + backend.unwatch_calls, | |
| 1650 | + Vec::<PathBuf>::new(), | |
| 1651 | + "no demotion happens without recursive coverage" | |
| 1652 | + ); | |
| 1653 | + assert!(backend.watched_paths.contains(parent.as_ref())); | |
| 1654 | + assert!( | |
| 1655 | + backend.watched_paths.contains(child.as_ref()), | |
| 1656 | + "the child keeps its own watch under a non-recursive backend" | |
| 1657 | + ); | |
| 1658 | + } | |
| 1659 | + | |
| 1660 | + // Removing the ancestor must not disturb the child: with no coverage there | |
| 1661 | + // is nothing to promote, so the child is neither re-watched nor rescanned. | |
| 1662 | + global.remove(parent_id); | |
| 1663 | + executor.run_until_parked(); | |
| 1664 | + { | |
| 1665 | + let backend = backend.lock(); | |
| 1666 | + assert_eq!( | |
| 1667 | + backend.unwatch_calls, | |
| 1668 | + &[parent.to_path_buf()], | |
| 1669 | + "only the ancestor is unwatched" | |
| 1670 | + ); | |
| 1671 | + assert_eq!( | |
| 1672 | + backend.watch_calls, | |
| 1673 | + &[child.to_path_buf(), parent.to_path_buf()], | |
| 1674 | + "the child is not re-watched when the ancestor goes away" | |
| 1675 | + ); | |
| 1676 | + assert!(backend.watched_paths.contains(child.as_ref())); | |
| 1677 | + } | |
| 1678 | + assert!( | |
| 1679 | + child_events.lock().is_empty(), | |
| 1680 | + "the child gets no rescan since its watch never lapsed" | |
| 1681 | + ); | |
| 1682 | + } | |
| 1683 | + | |
| 1571 | 1684 | #[gpui::test] |
| 1572 | 1685 | async fn removing_a_covered_child_issues_no_unwatch(executor: BackgroundExecutor) { |
| 1573 | 1686 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1574 | − let global = test_global(&executor, None, Some(backend.clone())); | |
| 1687 | + let global = test_global( | |
| 1688 | + &executor, | |
| 1689 | + None, | |
| 1690 | + Some((backend.clone(), RecursiveMode::Recursive)), | |
| 1691 | + ); | |
| 1575 | 1692 | let parent = Arc::<Path>::from(Path::new("/repo")); |
| 1576 | 1693 | let child = Arc::<Path>::from(Path::new("/repo/foo.csproj")); |
| 1577 | 1694 | |
| @@ -1602,7 +1719,11 @@ mod tests { · 24 unmodified lines | ||
| 1602 | 1719 | #[gpui::test] |
| 1603 | 1720 | async fn nested_registrations_keep_applied_as_an_antichain(executor: BackgroundExecutor) { |
| 1604 | 1721 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1605 | − let global = test_global(&executor, None, Some(backend.clone())); | |
| 1722 | + let global = test_global( | |
| 1723 | + &executor, | |
| 1724 | + None, | |
| 1725 | + Some((backend.clone(), RecursiveMode::Recursive)), | |
| 1726 | + ); | |
| 1606 | 1727 | let parent = Arc::<Path>::from(Path::new("/repo")); |
| 1607 | 1728 | let child = Arc::<Path>::from(Path::new("/repo/a")); |
| 1608 | 1729 | let grandchild = Arc::<Path>::from(Path::new("/repo/a/b")); |
| @@ -1632,7 +1753,11 @@ mod tests { · 23 unmodified lines | ||
| 1632 | 1753 | #[gpui::test] |
| 1633 | 1754 | async fn deferred_child_is_not_retained_as_intent_once_covered(executor: BackgroundExecutor) { |
| 1634 | 1755 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1635 | − let global = test_global(&executor, None, Some(backend.clone())); | |
| 1756 | + let global = test_global( | |
| 1757 | + &executor, | |
| 1758 | + None, | |
| 1759 | + Some((backend.clone(), RecursiveMode::Recursive)), | |
| 1760 | + ); | |
| 1636 | 1761 | let parent = Arc::<Path>::from(Path::new("/repo")); |
| 1637 | 1762 | let child = Arc::<Path>::from(Path::new("/repo/sub")); |
| 1638 | 1763 | |
| @@ -1673,7 +1798,14 @@ mod tests { · 34 unmodified lines | ||
| 1673 | 1798 | #[gpui::test] |
| 1674 | 1799 | async fn unregister_then_register_same_path_coalesces(executor: BackgroundExecutor) { |
| 1675 | 1800 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1676 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 1801 | + let global = test_global( | |
| 1802 | + &executor, | |
| 1803 | + Some(( | |
| 1804 | + backend.clone(), | |
| 1805 | + platform_recursive_mode(WatcherMode::Native), | |
| 1806 | + )), | |
| 1807 | + None, | |
| 1808 | + ); | |
| 1677 | 1809 | let path = Arc::<Path>::from(Path::new("/repo")); |
| 1678 | 1810 | |
| 1679 | 1811 | let first = global.add(path.clone(), WatcherMode::Native, noop_callback()); |
| @@ -1703,7 +1835,14 @@ mod tests { · 23 unmodified lines | ||
| 1703 | 1835 | #[gpui::test] |
| 1704 | 1836 | async fn failed_watch_is_not_retried_until_reregistered(executor: BackgroundExecutor) { |
| 1705 | 1837 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1706 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 1838 | + let global = test_global( | |
| 1839 | + &executor, | |
| 1840 | + Some(( | |
| 1841 | + backend.clone(), | |
| 1842 | + platform_recursive_mode(WatcherMode::Native), | |
| 1843 | + )), | |
| 1844 | + None, | |
| 1845 | + ); | |
| 1707 | 1846 | let path_a = Arc::<Path>::from(Path::new("/repo/a")); |
| 1708 | 1847 | let path_b = Arc::<Path>::from(Path::new("/repo/b")); |
| 1709 | 1848 | backend |
| @@ -1742,7 +1881,14 @@ mod tests { · 32 unmodified lines | ||
| 1742 | 1881 | #[gpui::test] |
| 1743 | 1882 | async fn batched_watch_skips_only_the_path_with_a_generic_error(executor: BackgroundExecutor) { |
| 1744 | 1883 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1745 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 1884 | + let global = test_global( | |
| 1885 | + &executor, | |
| 1886 | + Some(( | |
| 1887 | + backend.clone(), | |
| 1888 | + platform_recursive_mode(WatcherMode::Native), | |
| 1889 | + )), | |
| 1890 | + None, | |
| 1891 | + ); | |
| 1746 | 1892 | let path_a = Arc::<Path>::from(Path::new("/repo/a")); |
| 1747 | 1893 | let path_b = Arc::<Path>::from(Path::new("/repo/b")); |
| 1748 | 1894 | let path_c = Arc::<Path>::from(Path::new("/repo/c")); |
| @@ -1806,7 +1952,14 @@ mod tests { · 57 unmodified lines | ||
| 1806 | 1952 | executor: BackgroundExecutor, |
| 1807 | 1953 | ) { |
| 1808 | 1954 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1809 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 1955 | + let global = test_global( | |
| 1956 | + &executor, | |
| 1957 | + Some(( | |
| 1958 | + backend.clone(), | |
| 1959 | + platform_recursive_mode(WatcherMode::Native), | |
| 1960 | + )), | |
| 1961 | + None, | |
| 1962 | + ); | |
| 1810 | 1963 | let path_a = Arc::<Path>::from(Path::new("/repo/a")); |
| 1811 | 1964 | let path_b = Arc::<Path>::from(Path::new("/repo/b")); |
| 1812 | 1965 | let path_c = Arc::<Path>::from(Path::new("/repo/c")); |
| @@ -1866,7 +2019,11 @@ mod tests { · 53 unmodified lines | ||
| 1866 | 2019 | async fn events_are_dispatched_to_matching_mode_only(executor: BackgroundExecutor) { |
| 1867 | 2020 | let native_backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1868 | 2021 | let poll_backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1869 | − let global = test_global(&executor, Some(native_backend), Some(poll_backend)); | |
| 2022 | + let global = test_global( | |
| 2023 | + &executor, | |
| 2024 | + Some((native_backend, platform_recursive_mode(WatcherMode::Native))), | |
| 2025 | + Some((poll_backend, platform_recursive_mode(WatcherMode::Poll))), | |
| 2026 | + ); | |
| 1870 | 2027 | |
| 1871 | 2028 | let (native_events, native_callback) = collecting_callback(); |
| 1872 | 2029 | let (poll_events, poll_callback) = collecting_callback(); |
| @@ -1906,7 +2063,14 @@ mod tests { · 33 unmodified lines | ||
| 1906 | 2063 | #[gpui::test] |
| 1907 | 2064 | async fn cooldown_defers_watches_without_further_syscalls(executor: BackgroundExecutor) { |
| 1908 | 2065 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1909 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 2066 | + let global = test_global( | |
| 2067 | + &executor, | |
| 2068 | + Some(( | |
| 2069 | + backend.clone(), | |
| 2070 | + platform_recursive_mode(WatcherMode::Native), | |
| 2071 | + )), | |
| 2072 | + None, | |
| 2073 | + ); | |
| 1910 | 2074 | let path_a = Arc::<Path>::from(Path::new("/repo/a")); |
| 1911 | 2075 | let path_b = Arc::<Path>::from(Path::new("/repo/b")); |
| 1912 | 2076 | backend |
| @@ -1928,7 +2092,14 @@ mod tests { · 15 unmodified lines | ||
| 1928 | 2092 | #[gpui::test] |
| 1929 | 2093 | async fn deferred_watches_recover_after_cooldown(executor: BackgroundExecutor) { |
| 1930 | 2094 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1931 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 2095 | + let global = test_global( | |
| 2096 | + &executor, | |
| 2097 | + Some(( | |
| 2098 | + backend.clone(), | |
| 2099 | + platform_recursive_mode(WatcherMode::Native), | |
| 2100 | + )), | |
| 2101 | + None, | |
| 2102 | + ); | |
| 1932 | 2103 | let path_a = Arc::<Path>::from(Path::new("/repo/a")); |
| 1933 | 2104 | let path_b = Arc::<Path>::from(Path::new("/repo/b")); |
| 1934 | 2105 | backend |
| @@ -1964,7 +2135,14 @@ mod tests { · 29 unmodified lines | ||
| 1964 | 2135 | #[gpui::test] |
| 1965 | 2136 | async fn unwatch_clears_the_cooldown(executor: BackgroundExecutor) { |
| 1966 | 2137 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1967 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 2138 | + let global = test_global( | |
| 2139 | + &executor, | |
| 2140 | + Some(( | |
| 2141 | + backend.clone(), | |
| 2142 | + platform_recursive_mode(WatcherMode::Native), | |
| 2143 | + )), | |
| 2144 | + None, | |
| 2145 | + ); | |
| 1968 | 2146 | let path_a = Arc::<Path>::from(Path::new("/repo/a")); |
| 1969 | 2147 | let path_b = Arc::<Path>::from(Path::new("/repo/b")); |
| 1970 | 2148 | |
| @@ -1995,7 +2173,14 @@ mod tests { · 24 unmodified lines | ||
| 1995 | 2173 | #[gpui::test] |
| 1996 | 2174 | async fn unregistering_a_deferred_path_clears_it(executor: BackgroundExecutor) { |
| 1997 | 2175 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 1998 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 2176 | + let global = test_global( | |
| 2177 | + &executor, | |
| 2178 | + Some(( | |
| 2179 | + backend.clone(), | |
| 2180 | + platform_recursive_mode(WatcherMode::Native), | |
| 2181 | + )), | |
| 2182 | + None, | |
| 2183 | + ); | |
| 1999 | 2184 | let path = Arc::<Path>::from(Path::new("/repo/a")); |
| 2000 | 2185 | backend |
| 2001 | 2186 | .lock() |
| @@ -2030,7 +2215,14 @@ mod tests { · 28 unmodified lines | ||
| 2030 | 2215 | #[gpui::test] |
| 2031 | 2216 | async fn stream_restart_failure_reestablishes_all_watches(executor: BackgroundExecutor) { |
| 2032 | 2217 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2033 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 2218 | + let global = test_global( | |
| 2219 | + &executor, | |
| 2220 | + Some(( | |
| 2221 | + backend.clone(), | |
| 2222 | + platform_recursive_mode(WatcherMode::Native), | |
| 2223 | + )), | |
| 2224 | + None, | |
| 2225 | + ); | |
| 2034 | 2226 | let path_a = Arc::<Path>::from(Path::new("/repo/a")); |
| 2035 | 2227 | let path_b = Arc::<Path>::from(Path::new("/repo/b")); |
| 2036 | 2228 | |
| @@ -2068,7 +2260,14 @@ mod tests { · 31 unmodified lines | ||
| 2068 | 2260 | #[gpui::test] |
| 2069 | 2261 | async fn unwatch_skips_the_failed_path_and_unwatches_the_rest(executor: BackgroundExecutor) { |
| 2070 | 2262 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2071 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 2263 | + let global = test_global( | |
| 2264 | + &executor, | |
| 2265 | + Some(( | |
| 2266 | + backend.clone(), | |
| 2267 | + platform_recursive_mode(WatcherMode::Native), | |
| 2268 | + )), | |
| 2269 | + None, | |
| 2270 | + ); | |
| 2072 | 2271 | let path_a = Arc::<Path>::from(Path::new("/repo/a")); |
| 2073 | 2272 | let path_b = Arc::<Path>::from(Path::new("/repo/b")); |
| 2074 | 2273 | let path_c = Arc::<Path>::from(Path::new("/repo/c")); |
| @@ -2111,7 +2310,14 @@ mod tests { · 36 unmodified lines | ||
| 2111 | 2310 | executor: BackgroundExecutor, |
| 2112 | 2311 | ) { |
| 2113 | 2312 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2114 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 2313 | + let global = test_global( | |
| 2314 | + &executor, | |
| 2315 | + Some(( | |
| 2316 | + backend.clone(), | |
| 2317 | + platform_recursive_mode(WatcherMode::Native), | |
| 2318 | + )), | |
| 2319 | + None, | |
| 2320 | + ); | |
| 2115 | 2321 | let path_a = Arc::<Path>::from(Path::new("/repo/a")); |
| 2116 | 2322 | let path_b = Arc::<Path>::from(Path::new("/repo/b")); |
| 2117 | 2323 | |
| @@ -2147,7 +2353,14 @@ mod tests { · 29 unmodified lines | ||
| 2147 | 2353 | executor: BackgroundExecutor, |
| 2148 | 2354 | ) { |
| 2149 | 2355 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2150 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 2356 | + let global = test_global( | |
| 2357 | + &executor, | |
| 2358 | + Some(( | |
| 2359 | + backend.clone(), | |
| 2360 | + platform_recursive_mode(WatcherMode::Native), | |
| 2361 | + )), | |
| 2362 | + None, | |
| 2363 | + ); | |
| 2151 | 2364 | let path = Arc::<Path>::from(Path::new("/repo")); |
| 2152 | 2365 | |
| 2153 | 2366 | // The initial watch applies in the backend, but the stream fails to |
| @@ -2175,7 +2388,14 @@ mod tests { · 21 unmodified lines | ||
| 2175 | 2388 | #[gpui::test] |
| 2176 | 2389 | async fn flush_resolves_after_reconcile(executor: BackgroundExecutor) { |
| 2177 | 2390 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2178 | − let global = test_global(&executor, Some(backend.clone()), None); | |
| 2391 | + let global = test_global( | |
| 2392 | + &executor, | |
| 2393 | + Some(( | |
| 2394 | + backend.clone(), | |
| 2395 | + platform_recursive_mode(WatcherMode::Native), | |
| 2396 | + )), | |
| 2397 | + None, | |
| 2398 | + ); | |
| 2179 | 2399 | let path = Arc::<Path>::from(Path::new("/repo")); |
| 2180 | 2400 | |
| 2181 | 2401 | global.add(path.clone(), WatcherMode::Native, noop_callback()); |
| @@ -2191,7 +2411,11 @@ mod tests { · 9 unmodified lines | ||
| 2191 | 2411 | executor: BackgroundExecutor, |
| 2192 | 2412 | ) { |
| 2193 | 2413 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2194 | − let global = test_global(&executor, Some(backend), None); | |
| 2414 | + let global = test_global( | |
| 2415 | + &executor, | |
| 2416 | + Some((backend, platform_recursive_mode(WatcherMode::Native))), | |
| 2417 | + None, | |
| 2418 | + ); | |
| 2195 | 2419 | |
| 2196 | 2420 | let mut health = global.check_health(); |
| 2197 | 2421 | executor.run_until_parked(); |
| @@ -2201,8 +2425,8 @@ mod tests { · 3 unmodified lines | ||
| 2201 | 2425 | |
| 2202 | 2426 | fn test_sink( |
| 2203 | 2427 | executor: &BackgroundExecutor, |
| 2204 | − native_backend: Option<Arc<Mutex<FakeWatchBackend>>>, | |
| 2205 | − poll_backend: Option<Arc<Mutex<FakeWatchBackend>>>, | |
| 2428 | + native_backend: FakeBackend, | |
| 2429 | + poll_backend: FakeBackend, | |
| 2206 | 2430 | ) -> ( |
| 2207 | 2431 | FsWatcher, |
| 2208 | 2432 | async_channel::Receiver<()>, |
| @@ -2210,10 +2434,8 @@ mod tests { · 1 unmodified line | ||
| 2210 | 2434 | ) { |
| 2211 | 2435 | let global = GlobalWatcher::with_backends( |
| 2212 | 2436 | executor, |
| 2213 | − native_backend | |
| 2214 | − .map(|backend| Box::new(SharedFakeWatchBackend(backend)) as Box<dyn WatchBackend>), | |
| 2215 | − poll_backend | |
| 2216 | − .map(|backend| Box::new(SharedFakeWatchBackend(backend)) as Box<dyn WatchBackend>), | |
| 2437 | + fake_backend(native_backend, WatcherMode::Native), | |
| 2438 | + fake_backend(poll_backend, WatcherMode::Poll), | |
| 2217 | 2439 | ); |
| 2218 | 2440 | let (tx, rx) = async_channel::unbounded(); |
| 2219 | 2441 | let pending_path_events: Arc<Mutex<Vec<PathEvent>>> = Default::default(); |
| @@ -2224,7 +2446,14 @@ mod tests { · 4 unmodified lines | ||
| 2224 | 2446 | #[gpui::test] |
| 2225 | 2447 | async fn sink_establishes_watch_for_existing_path(executor: BackgroundExecutor) { |
| 2226 | 2448 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2227 | − let (sink, _rx, _pending) = test_sink(&executor, Some(backend.clone()), None); | |
| 2449 | + let (sink, _rx, _pending) = test_sink( | |
| 2450 | + &executor, | |
| 2451 | + Some(( | |
| 2452 | + backend.clone(), | |
| 2453 | + platform_recursive_mode(WatcherMode::Native), | |
| 2454 | + )), | |
| 2455 | + None, | |
| 2456 | + ); | |
| 2228 | 2457 | let dir = tempfile::TempDir::new().expect("create temp dir"); |
| 2229 | 2458 | |
| 2230 | 2459 | sink.add(dir.path()).expect("add succeeds"); |
| @@ -2242,7 +2471,14 @@ mod tests { · 11 unmodified lines | ||
| 2242 | 2471 | #[gpui::test] |
| 2243 | 2472 | async fn sink_waits_for_path_creation(executor: BackgroundExecutor) { |
| 2244 | 2473 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2245 | − let (sink, rx, pending_path_events) = test_sink(&executor, Some(backend.clone()), None); | |
| 2474 | + let (sink, rx, pending_path_events) = test_sink( | |
| 2475 | + &executor, | |
| 2476 | + Some(( | |
| 2477 | + backend.clone(), | |
| 2478 | + platform_recursive_mode(WatcherMode::Native), | |
| 2479 | + )), | |
| 2480 | + None, | |
| 2481 | + ); | |
| 2246 | 2482 | let dir = tempfile::TempDir::new().expect("create temp dir"); |
| 2247 | 2483 | let file = dir.path().join("missing.txt"); |
| 2248 | 2484 | |
| @@ -2271,7 +2507,14 @@ mod tests { · 22 unmodified lines | ||
| 2271 | 2507 | #[gpui::test] |
| 2272 | 2508 | async fn sink_remove_cancels_pending_watch(executor: BackgroundExecutor) { |
| 2273 | 2509 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2274 | − let (sink, _rx, _pending) = test_sink(&executor, Some(backend.clone()), None); | |
| 2510 | + let (sink, _rx, _pending) = test_sink( | |
| 2511 | + &executor, | |
| 2512 | + Some(( | |
| 2513 | + backend.clone(), | |
| 2514 | + platform_recursive_mode(WatcherMode::Native), | |
| 2515 | + )), | |
| 2516 | + None, | |
| 2517 | + ); | |
| 2275 | 2518 | let dir = tempfile::TempDir::new().expect("create temp dir"); |
| 2276 | 2519 | let file = dir.path().join("missing.txt"); |
| 2277 | 2520 | |
| @@ -2291,7 +2534,14 @@ mod tests { · 13 unmodified lines | ||
| 2291 | 2534 | #[gpui::test] |
| 2292 | 2535 | async fn sink_recovers_deferred_watch_and_emits_rescan(executor: BackgroundExecutor) { |
| 2293 | 2536 | let backend = Arc::new(Mutex::new(FakeWatchBackend::default())); |
| 2294 | − let (sink, _rx, pending_path_events) = test_sink(&executor, Some(backend.clone()), None); | |
| 2537 | + let (sink, _rx, pending_path_events) = test_sink( | |
| 2538 | + &executor, | |
| 2539 | + Some(( | |
| 2540 | + backend.clone(), | |
| 2541 | + platform_recursive_mode(WatcherMode::Native), | |
| 2542 | + )), | |
| 2543 | + None, | |
| 2544 | + ); | |
| 2295 | 2545 | let dir = tempfile::TempDir::new().expect("create temp dir"); |
| 2296 | 2546 | backend |
| 2297 | 2547 | .lock() |