History

parametrize on recursiveness

Cole Miller committed 5 months ago

Commit 2df437e1 parent 374f17b1 file · +302 −52Browse files

Unified · Split

crates/fs/src/fs_watcher.rs+302−52
@@ -76,7 +76,7 @@ impl Watcher for FsWatcher { · 75 unmodified lines
76 paths.get(ancestor).is_some_and(|entry| match entry {76 paths.get(ancestor).is_some_and(|entry| match entry {
77 FsWatcherRegistration::Pending { .. } => false,77 FsWatcherRegistration::Pending { .. } => false,
78 FsWatcherRegistration::Registered { mode, .. } => {78 FsWatcherRegistration::Registered { mode, .. } => {
79 recursive_mode(*mode) == notify::RecursiveMode::Recursive79 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/// `GlobalWatcher` talks to a `WatchBackend`, which handles the OS-specific details of filesystem watching.556/// `GlobalWatcher` talks to a `WatchBackend`, which handles the OS-specific details of filesystem watching.
557/// In production, we use the `notify` crate for this. The job of `GlobalWatcher` is to convey watch and unwatch557/// 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 pub(crate) fn new(executor: &BackgroundExecutor) -> Self {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 fn with_backends(658 fn with_backends(
655 executor: &BackgroundExecutor,659 executor: &BackgroundExecutor,
656 native_backend: Option<Box<dyn WatchBackend>>,660 native: (Option<Box<dyn WatchBackend>>, notify::RecursiveMode),
657 poll_backend: Option<Box<dyn WatchBackend>>,661 poll: (Option<Box<dyn WatchBackend>>, notify::RecursiveMode),
658 ) -> Self {662 ) -> Self {
659 let state = Arc::new(Mutex::new(WatcherState {663 let state = Arc::new(Mutex::new(WatcherState {
660 watchers: HashMap::new(),664 watchers: HashMap::new(),
@@ -672,10 +676,11 @@ impl GlobalWatcher { · 11 unmodified lines
672 executor: executor.clone(),676 executor: executor.clone(),
673 native: BackendState::new(677 native: BackendState::new(
674 WatcherMode::Native,678 WatcherMode::Native,
679 native.1,
675 executor.clone(),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 .run(wake_rx),685 .run(wake_rx),
681 )686 )
@@ -690,6 +695,10 @@ impl GlobalWatcher { · 8 unmodified lines
690 695
691struct BackendState {696struct BackendState {
692 mode: WatcherMode,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 executor: BackgroundExecutor,702 executor: BackgroundExecutor,
694 backend: Option<Box<dyn WatchBackend>>,703 backend: Option<Box<dyn WatchBackend>>,
695 /// Paths that we have successfully asked the backend to watch.704 /// Paths that we have successfully asked the backend to watch.
@@ -753,11 +762,11 @@ impl Reconciler { · 57 unmodified lines
753 health_checks = std::mem::take(&mut state.pending_health_checks);762 health_checks = std::mem::take(&mut state.pending_health_checks);
754 let dirty = std::mem::take(&mut state.dirty_paths);763 let dirty = std::mem::take(&mut state.dirty_paths);
755 for (mode, path) in dirty {764 for (mode, path) in dirty {
756 let affected_paths = match mode {765 let (affected_paths, is_recursive) = match mode {
757 WatcherMode::Native => &mut native_affected_paths,766 WatcherMode::Native => (&mut native_affected_paths, self.native.is_recursive()),
758 WatcherMode::Poll => &mut poll_affected_paths,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 for (descendant, _) in state.path_registrations(mode).range::<Path, _>((770 for (descendant, _) in state.path_registrations(mode).range::<Path, _>((
762 std::ops::Bound::Excluded(path.as_ref()),771 std::ops::Bound::Excluded(path.as_ref()),
763 std::ops::Bound::Unbounded,772 std::ops::Bound::Unbounded,
@@ -797,11 +806,13 @@ impl Reconciler { · 33 unmodified lines
797impl BackendState {806impl BackendState {
798 fn new(807 fn new(
799 mode: WatcherMode,808 mode: WatcherMode,
809 recursive_mode: notify::RecursiveMode,
800 executor: BackgroundExecutor,810 executor: BackgroundExecutor,
801 backend: Option<Box<dyn WatchBackend>>,811 backend: Option<Box<dyn WatchBackend>>,
802 ) -> Self {812 ) -> Self {
803 Self {813 Self {
804 mode,814 mode,
815 recursive_mode,
805 executor,816 executor,
806 backend,817 backend,
807 applied_paths: HashSet::new(),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 fn reconcile(829 fn reconcile(
815 &mut self,830 &mut self,
816 affected_paths: HashSet<Arc<Path>>,831 affected_paths: HashSet<Arc<Path>>,
@@ -829,7 +844,7 @@ impl BackendState { · 12 unmodified lines
829 let registrations = watcher_state.path_registrations(self.mode);844 let registrations = watcher_state.path_registrations(self.mode);
830 for path in affected_paths {845 for path in affected_paths {
831 let is_desired = registrations.contains_key(&path);846 let is_desired = registrations.contains_key(&path);
832 let is_covered = recursive_mode(self.mode) == notify::RecursiveMode::Recursive847 let is_covered = self.is_recursive()
833 && path848 && path
834 .ancestors()849 .ancestors()
835 .skip(1)850 .skip(1)
@@ -938,7 +953,7 @@ impl BackendState { · 102 unmodified lines
938 953
939 // Check that no path in the applied set is a strict child of another,954 // Check that no path in the applied set is a strict child of another,
940 // if the backend is recursive, since this would be redundant.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 let mut applied_paths = self957 let mut applied_paths = self
943 .applied_paths958 .applied_paths
944 .iter()959 .iter()
@@ -1017,7 +1032,7 @@ impl BackendState { · 72 unmodified lines
1017 paths: Vec<Arc<Path>>,1032 paths: Vec<Arc<Path>>,
1018 watcher_state: &Arc<Mutex<WatcherState>>,1033 watcher_state: &Arc<Mutex<WatcherState>>,
1019 ) -> Option<Instant> {1034 ) -> Option<Instant> {
1020 let recursive_mode = recursive_mode(self.mode);1035 let recursive_mode = self.recursive_mode;
1021 let mut queue = paths;1036 let mut queue = paths;
1022 let mut wake_at = None;1037 let mut wake_at = None;
1023 while !queue.is_empty() {1038 while !queue.is_empty() {
@@ -1195,7 +1210,7 @@ fn create_backend( · 171 unmodified lines
1195 }1210 }
1196}1211}
1197 1212
1198fn recursive_mode(mode: WatcherMode) -> notify::RecursiveMode {1213fn platform_recursive_mode(mode: WatcherMode) -> notify::RecursiveMode {
1199 match mode {1214 match mode {
1200 WatcherMode::Native => {1215 WatcherMode::Native => {
1201 if cfg!(any(target_os = "windows", target_os = "macos")) {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#[cfg(test)]1289#[cfg(test)]
1275mod tests {1290mod tests {
1276 use super::*;1291 use super::*;
1292 use notify::RecursiveMode;
1277 use std::{collections::HashSet, path::PathBuf};1293 use std::{collections::HashSet, path::PathBuf};
1278 1294
1279 fn rescan(path: &str) -> PathEvent {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 fn test_global(1495 fn test_global(
1465 executor: &BackgroundExecutor,1496 executor: &BackgroundExecutor,
1466 native_backend: Option<Arc<Mutex<FakeWatchBackend>>>,1497 native_backend: FakeBackend,
1467 poll_backend: Option<Arc<Mutex<FakeWatchBackend>>>,1498 poll_backend: FakeBackend,
1468 ) -> GlobalWatcher {1499 ) -> GlobalWatcher {
1469 GlobalWatcher::with_backends(1500 GlobalWatcher::with_backends(
1470 executor,1501 executor,
1471 native_backend1502 fake_backend(native_backend, WatcherMode::Native),
1472 .map(|backend| Box::new(SharedFakeWatchBackend(backend)) as Box<dyn WatchBackend>),1503 fake_backend(poll_backend, WatcherMode::Poll),
1473 poll_backend
1474 .map(|backend| Box::new(SharedFakeWatchBackend(backend)) as Box<dyn WatchBackend>),
1475 )1504 )
1476 }1505 }
1477 1506
@@ -1495,7 +1524,14 @@ mod tests { · 17 unmodified lines
1495 #[gpui::test]1524 #[gpui::test]
1496 async fn watch_and_unwatch_call_the_backend_once_per_path(executor: BackgroundExecutor) {1525 async fn watch_and_unwatch_call_the_backend_once_per_path(executor: BackgroundExecutor) {
1497 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path = Arc::<Path>::from(Path::new("/repo"));1535 let path = Arc::<Path>::from(Path::new("/repo"));
1500 1536
1501 let first = global.add(path.clone(), WatcherMode::Native, noop_callback());1537 let first = global.add(path.clone(), WatcherMode::Native, noop_callback());
@@ -1516,7 +1552,11 @@ mod tests { · 14 unmodified lines
1516 #[gpui::test]1552 #[gpui::test]
1517 async fn covered_child_is_promoted_when_parent_is_unregistered(executor: BackgroundExecutor) {1553 async fn covered_child_is_promoted_when_parent_is_unregistered(executor: BackgroundExecutor) {
1518 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let parent = Arc::<Path>::from(Path::new("/repo"));1560 let parent = Arc::<Path>::from(Path::new("/repo"));
1521 let child = Arc::<Path>::from(Path::new("/repo/foo.csproj"));1561 let child = Arc::<Path>::from(Path::new("/repo/foo.csproj"));
1522 1562
@@ -1543,7 +1583,11 @@ mod tests { · 20 unmodified lines
1543 #[gpui::test]1583 #[gpui::test]
1544 async fn child_is_demoted_when_covering_parent_is_registered(executor: BackgroundExecutor) {1584 async fn child_is_demoted_when_covering_parent_is_registered(executor: BackgroundExecutor) {
1545 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let parent = Arc::<Path>::from(Path::new("/repo"));1591 let parent = Arc::<Path>::from(Path::new("/repo"));
1548 let child = Arc::<Path>::from(Path::new("/repo/sub"));1592 let child = Arc::<Path>::from(Path::new("/repo/sub"));
1549 1593
@@ -1568,10 +1612,83 @@ mod tests { · 18 unmodified lines
1568 assert!(!backend.watched_paths.contains(child.as_ref()));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 #[gpui::test]1684 #[gpui::test]
1572 async fn removing_a_covered_child_issues_no_unwatch(executor: BackgroundExecutor) {1685 async fn removing_a_covered_child_issues_no_unwatch(executor: BackgroundExecutor) {
1573 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let parent = Arc::<Path>::from(Path::new("/repo"));1692 let parent = Arc::<Path>::from(Path::new("/repo"));
1576 let child = Arc::<Path>::from(Path::new("/repo/foo.csproj"));1693 let child = Arc::<Path>::from(Path::new("/repo/foo.csproj"));
1577 1694
@@ -1602,7 +1719,11 @@ mod tests { · 24 unmodified lines
1602 #[gpui::test]1719 #[gpui::test]
1603 async fn nested_registrations_keep_applied_as_an_antichain(executor: BackgroundExecutor) {1720 async fn nested_registrations_keep_applied_as_an_antichain(executor: BackgroundExecutor) {
1604 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let parent = Arc::<Path>::from(Path::new("/repo"));1727 let parent = Arc::<Path>::from(Path::new("/repo"));
1607 let child = Arc::<Path>::from(Path::new("/repo/a"));1728 let child = Arc::<Path>::from(Path::new("/repo/a"));
1608 let grandchild = Arc::<Path>::from(Path::new("/repo/a/b"));1729 let grandchild = Arc::<Path>::from(Path::new("/repo/a/b"));
@@ -1632,7 +1753,11 @@ mod tests { · 23 unmodified lines
1632 #[gpui::test]1753 #[gpui::test]
1633 async fn deferred_child_is_not_retained_as_intent_once_covered(executor: BackgroundExecutor) {1754 async fn deferred_child_is_not_retained_as_intent_once_covered(executor: BackgroundExecutor) {
1634 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let parent = Arc::<Path>::from(Path::new("/repo"));1761 let parent = Arc::<Path>::from(Path::new("/repo"));
1637 let child = Arc::<Path>::from(Path::new("/repo/sub"));1762 let child = Arc::<Path>::from(Path::new("/repo/sub"));
1638 1763
@@ -1673,7 +1798,14 @@ mod tests { · 34 unmodified lines
1673 #[gpui::test]1798 #[gpui::test]
1674 async fn unregister_then_register_same_path_coalesces(executor: BackgroundExecutor) {1799 async fn unregister_then_register_same_path_coalesces(executor: BackgroundExecutor) {
1675 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path = Arc::<Path>::from(Path::new("/repo"));1809 let path = Arc::<Path>::from(Path::new("/repo"));
1678 1810
1679 let first = global.add(path.clone(), WatcherMode::Native, noop_callback());1811 let first = global.add(path.clone(), WatcherMode::Native, noop_callback());
@@ -1703,7 +1835,14 @@ mod tests { · 23 unmodified lines
1703 #[gpui::test]1835 #[gpui::test]
1704 async fn failed_watch_is_not_retried_until_reregistered(executor: BackgroundExecutor) {1836 async fn failed_watch_is_not_retried_until_reregistered(executor: BackgroundExecutor) {
1705 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path_a = Arc::<Path>::from(Path::new("/repo/a"));1846 let path_a = Arc::<Path>::from(Path::new("/repo/a"));
1708 let path_b = Arc::<Path>::from(Path::new("/repo/b"));1847 let path_b = Arc::<Path>::from(Path::new("/repo/b"));
1709 backend1848 backend
@@ -1742,7 +1881,14 @@ mod tests { · 32 unmodified lines
1742 #[gpui::test]1881 #[gpui::test]
1743 async fn batched_watch_skips_only_the_path_with_a_generic_error(executor: BackgroundExecutor) {1882 async fn batched_watch_skips_only_the_path_with_a_generic_error(executor: BackgroundExecutor) {
1744 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path_a = Arc::<Path>::from(Path::new("/repo/a"));1892 let path_a = Arc::<Path>::from(Path::new("/repo/a"));
1747 let path_b = Arc::<Path>::from(Path::new("/repo/b"));1893 let path_b = Arc::<Path>::from(Path::new("/repo/b"));
1748 let path_c = Arc::<Path>::from(Path::new("/repo/c"));1894 let path_c = Arc::<Path>::from(Path::new("/repo/c"));
@@ -1806,7 +1952,14 @@ mod tests { · 57 unmodified lines
1806 executor: BackgroundExecutor,1952 executor: BackgroundExecutor,
1807 ) {1953 ) {
1808 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path_a = Arc::<Path>::from(Path::new("/repo/a"));1963 let path_a = Arc::<Path>::from(Path::new("/repo/a"));
1811 let path_b = Arc::<Path>::from(Path::new("/repo/b"));1964 let path_b = Arc::<Path>::from(Path::new("/repo/b"));
1812 let path_c = Arc::<Path>::from(Path::new("/repo/c"));1965 let path_c = Arc::<Path>::from(Path::new("/repo/c"));
@@ -1866,7 +2019,11 @@ mod tests { · 53 unmodified lines
1866 async fn events_are_dispatched_to_matching_mode_only(executor: BackgroundExecutor) {2019 async fn events_are_dispatched_to_matching_mode_only(executor: BackgroundExecutor) {
1867 let native_backend = Arc::new(Mutex::new(FakeWatchBackend::default()));2020 let native_backend = Arc::new(Mutex::new(FakeWatchBackend::default()));
1868 let poll_backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let (native_events, native_callback) = collecting_callback();2028 let (native_events, native_callback) = collecting_callback();
1872 let (poll_events, poll_callback) = collecting_callback();2029 let (poll_events, poll_callback) = collecting_callback();
@@ -1906,7 +2063,14 @@ mod tests { · 33 unmodified lines
1906 #[gpui::test]2063 #[gpui::test]
1907 async fn cooldown_defers_watches_without_further_syscalls(executor: BackgroundExecutor) {2064 async fn cooldown_defers_watches_without_further_syscalls(executor: BackgroundExecutor) {
1908 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path_a = Arc::<Path>::from(Path::new("/repo/a"));2074 let path_a = Arc::<Path>::from(Path::new("/repo/a"));
1911 let path_b = Arc::<Path>::from(Path::new("/repo/b"));2075 let path_b = Arc::<Path>::from(Path::new("/repo/b"));
1912 backend2076 backend
@@ -1928,7 +2092,14 @@ mod tests { · 15 unmodified lines
1928 #[gpui::test]2092 #[gpui::test]
1929 async fn deferred_watches_recover_after_cooldown(executor: BackgroundExecutor) {2093 async fn deferred_watches_recover_after_cooldown(executor: BackgroundExecutor) {
1930 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path_a = Arc::<Path>::from(Path::new("/repo/a"));2103 let path_a = Arc::<Path>::from(Path::new("/repo/a"));
1933 let path_b = Arc::<Path>::from(Path::new("/repo/b"));2104 let path_b = Arc::<Path>::from(Path::new("/repo/b"));
1934 backend2105 backend
@@ -1964,7 +2135,14 @@ mod tests { · 29 unmodified lines
1964 #[gpui::test]2135 #[gpui::test]
1965 async fn unwatch_clears_the_cooldown(executor: BackgroundExecutor) {2136 async fn unwatch_clears_the_cooldown(executor: BackgroundExecutor) {
1966 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path_a = Arc::<Path>::from(Path::new("/repo/a"));2146 let path_a = Arc::<Path>::from(Path::new("/repo/a"));
1969 let path_b = Arc::<Path>::from(Path::new("/repo/b"));2147 let path_b = Arc::<Path>::from(Path::new("/repo/b"));
1970 2148
@@ -1995,7 +2173,14 @@ mod tests { · 24 unmodified lines
1995 #[gpui::test]2173 #[gpui::test]
1996 async fn unregistering_a_deferred_path_clears_it(executor: BackgroundExecutor) {2174 async fn unregistering_a_deferred_path_clears_it(executor: BackgroundExecutor) {
1997 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path = Arc::<Path>::from(Path::new("/repo/a"));2184 let path = Arc::<Path>::from(Path::new("/repo/a"));
2000 backend2185 backend
2001 .lock()2186 .lock()
@@ -2030,7 +2215,14 @@ mod tests { · 28 unmodified lines
2030 #[gpui::test]2215 #[gpui::test]
2031 async fn stream_restart_failure_reestablishes_all_watches(executor: BackgroundExecutor) {2216 async fn stream_restart_failure_reestablishes_all_watches(executor: BackgroundExecutor) {
2032 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path_a = Arc::<Path>::from(Path::new("/repo/a"));2226 let path_a = Arc::<Path>::from(Path::new("/repo/a"));
2035 let path_b = Arc::<Path>::from(Path::new("/repo/b"));2227 let path_b = Arc::<Path>::from(Path::new("/repo/b"));
2036 2228
@@ -2068,7 +2260,14 @@ mod tests { · 31 unmodified lines
2068 #[gpui::test]2260 #[gpui::test]
2069 async fn unwatch_skips_the_failed_path_and_unwatches_the_rest(executor: BackgroundExecutor) {2261 async fn unwatch_skips_the_failed_path_and_unwatches_the_rest(executor: BackgroundExecutor) {
2070 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path_a = Arc::<Path>::from(Path::new("/repo/a"));2271 let path_a = Arc::<Path>::from(Path::new("/repo/a"));
2073 let path_b = Arc::<Path>::from(Path::new("/repo/b"));2272 let path_b = Arc::<Path>::from(Path::new("/repo/b"));
2074 let path_c = Arc::<Path>::from(Path::new("/repo/c"));2273 let path_c = Arc::<Path>::from(Path::new("/repo/c"));
@@ -2111,7 +2310,14 @@ mod tests { · 36 unmodified lines
2111 executor: BackgroundExecutor,2310 executor: BackgroundExecutor,
2112 ) {2311 ) {
2113 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path_a = Arc::<Path>::from(Path::new("/repo/a"));2321 let path_a = Arc::<Path>::from(Path::new("/repo/a"));
2116 let path_b = Arc::<Path>::from(Path::new("/repo/b"));2322 let path_b = Arc::<Path>::from(Path::new("/repo/b"));
2117 2323
@@ -2147,7 +2353,14 @@ mod tests { · 29 unmodified lines
2147 executor: BackgroundExecutor,2353 executor: BackgroundExecutor,
2148 ) {2354 ) {
2149 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path = Arc::<Path>::from(Path::new("/repo"));2364 let path = Arc::<Path>::from(Path::new("/repo"));
2152 2365
2153 // The initial watch applies in the backend, but the stream fails to2366 // The initial watch applies in the backend, but the stream fails to
@@ -2175,7 +2388,14 @@ mod tests { · 21 unmodified lines
2175 #[gpui::test]2388 #[gpui::test]
2176 async fn flush_resolves_after_reconcile(executor: BackgroundExecutor) {2389 async fn flush_resolves_after_reconcile(executor: BackgroundExecutor) {
2177 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let path = Arc::<Path>::from(Path::new("/repo"));2399 let path = Arc::<Path>::from(Path::new("/repo"));
2180 2400
2181 global.add(path.clone(), WatcherMode::Native, noop_callback());2401 global.add(path.clone(), WatcherMode::Native, noop_callback());
@@ -2191,7 +2411,11 @@ mod tests { · 9 unmodified lines
2191 executor: BackgroundExecutor,2411 executor: BackgroundExecutor,
2192 ) {2412 ) {
2193 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let mut health = global.check_health();2420 let mut health = global.check_health();
2197 executor.run_until_parked();2421 executor.run_until_parked();
@@ -2201,8 +2425,8 @@ mod tests { · 3 unmodified lines
2201 2425
2202 fn test_sink(2426 fn test_sink(
2203 executor: &BackgroundExecutor,2427 executor: &BackgroundExecutor,
2204 native_backend: Option<Arc<Mutex<FakeWatchBackend>>>,2428 native_backend: FakeBackend,
2205 poll_backend: Option<Arc<Mutex<FakeWatchBackend>>>,2429 poll_backend: FakeBackend,
2206 ) -> (2430 ) -> (
2207 FsWatcher,2431 FsWatcher,
2208 async_channel::Receiver<()>,2432 async_channel::Receiver<()>,
@@ -2210,10 +2434,8 @@ mod tests { · 1 unmodified line
2210 ) {2434 ) {
2211 let global = GlobalWatcher::with_backends(2435 let global = GlobalWatcher::with_backends(
2212 executor,2436 executor,
2213 native_backend2437 fake_backend(native_backend, WatcherMode::Native),
2214 .map(|backend| Box::new(SharedFakeWatchBackend(backend)) as Box<dyn WatchBackend>),2438 fake_backend(poll_backend, WatcherMode::Poll),
2215 poll_backend
2216 .map(|backend| Box::new(SharedFakeWatchBackend(backend)) as Box<dyn WatchBackend>),
2217 );2439 );
2218 let (tx, rx) = async_channel::unbounded();2440 let (tx, rx) = async_channel::unbounded();
2219 let pending_path_events: Arc<Mutex<Vec<PathEvent>>> = Default::default();2441 let pending_path_events: Arc<Mutex<Vec<PathEvent>>> = Default::default();
@@ -2224,7 +2446,14 @@ mod tests { · 4 unmodified lines
2224 #[gpui::test]2446 #[gpui::test]
2225 async fn sink_establishes_watch_for_existing_path(executor: BackgroundExecutor) {2447 async fn sink_establishes_watch_for_existing_path(executor: BackgroundExecutor) {
2226 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let dir = tempfile::TempDir::new().expect("create temp dir");2457 let dir = tempfile::TempDir::new().expect("create temp dir");
2229 2458
2230 sink.add(dir.path()).expect("add succeeds");2459 sink.add(dir.path()).expect("add succeeds");
@@ -2242,7 +2471,14 @@ mod tests { · 11 unmodified lines
2242 #[gpui::test]2471 #[gpui::test]
2243 async fn sink_waits_for_path_creation(executor: BackgroundExecutor) {2472 async fn sink_waits_for_path_creation(executor: BackgroundExecutor) {
2244 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let dir = tempfile::TempDir::new().expect("create temp dir");2482 let dir = tempfile::TempDir::new().expect("create temp dir");
2247 let file = dir.path().join("missing.txt");2483 let file = dir.path().join("missing.txt");
2248 2484
@@ -2271,7 +2507,14 @@ mod tests { · 22 unmodified lines
2271 #[gpui::test]2507 #[gpui::test]
2272 async fn sink_remove_cancels_pending_watch(executor: BackgroundExecutor) {2508 async fn sink_remove_cancels_pending_watch(executor: BackgroundExecutor) {
2273 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let dir = tempfile::TempDir::new().expect("create temp dir");2518 let dir = tempfile::TempDir::new().expect("create temp dir");
2276 let file = dir.path().join("missing.txt");2519 let file = dir.path().join("missing.txt");
2277 2520
@@ -2291,7 +2534,14 @@ mod tests { · 13 unmodified lines
2291 #[gpui::test]2534 #[gpui::test]
2292 async fn sink_recovers_deferred_watch_and_emits_rescan(executor: BackgroundExecutor) {2535 async fn sink_recovers_deferred_watch_and_emits_rescan(executor: BackgroundExecutor) {
2293 let backend = Arc::new(Mutex::new(FakeWatchBackend::default()));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 let dir = tempfile::TempDir::new().expect("create temp dir");2545 let dir = tempfile::TempDir::new().expect("create temp dir");
2296 backend2546 backend
2297 .lock()2547 .lock()