2141 lines · 63.1 KB · Rust
RawDownload
use crate::{    worktree::{Event, Snapshot, WorktreeModelHandle},    Entry, EntryKind, PathChange, Worktree,};use anyhow::Result;use client::Client;use fs::{repository::GitFileStatus, FakeFs, Fs, RealFs, RemoveOptions};use git::GITIGNORE;use gpui::{executor::Deterministic, ModelContext, Task, TestAppContext};use parking_lot::Mutex;use postage::stream::Stream;use pretty_assertions::assert_eq;use rand::prelude::*;use serde_json::json;use std::{    env,    fmt::Write,    mem,    path::{Path, PathBuf},    sync::Arc,};use util::{http::FakeHttpClient, test::temp_tree, ResultExt}; #[gpui::test]async fn test_traversal(cx: &mut TestAppContext) {    let fs = FakeFs::new(cx.background());    fs.insert_tree(        "/root",        json!({           ".gitignore": "a/b\n",           "a": {               "b": "",               "c": "",           }        }),    )    .await;     let tree = Worktree::local(        build_client(cx),        Path::new("/root"),        true,        fs,        Default::default(),        &mut cx.to_async(),    )    .await    .unwrap();    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())        .await;     tree.read_with(cx, |tree, _| {        assert_eq!(            tree.entries(false)                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![                Path::new(""),                Path::new(".gitignore"),                Path::new("a"),                Path::new("a/c"),            ]        );        assert_eq!(            tree.entries(true)                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![                Path::new(""),                Path::new(".gitignore"),                Path::new("a"),                Path::new("a/b"),                Path::new("a/c"),            ]        );    })} #[gpui::test]async fn test_descendent_entries(cx: &mut TestAppContext) {    let fs = FakeFs::new(cx.background());    fs.insert_tree(        "/root",        json!({            "a": "",            "b": {               "c": {                   "d": ""               },               "e": {}            },            "f": "",            "g": {                "h": {}            },            "i": {                "j": {                    "k": ""                },                "l": {                 }            },            ".gitignore": "i/j\n",        }),    )    .await;     let tree = Worktree::local(        build_client(cx),        Path::new("/root"),        true,        fs,        Default::default(),        &mut cx.to_async(),    )    .await    .unwrap();    cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())        .await;     tree.read_with(cx, |tree, _| {        assert_eq!(            tree.descendent_entries(false, false, Path::new("b"))                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![Path::new("b/c/d"),]        );        assert_eq!(            tree.descendent_entries(true, false, Path::new("b"))                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![                Path::new("b"),                Path::new("b/c"),                Path::new("b/c/d"),                Path::new("b/e"),            ]        );         assert_eq!(            tree.descendent_entries(false, false, Path::new("g"))                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            Vec::<PathBuf>::new()        );        assert_eq!(            tree.descendent_entries(true, false, Path::new("g"))                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![Path::new("g"), Path::new("g/h"),]        );    });     // Expand gitignored directory.    tree.read_with(cx, |tree, _| {        tree.as_local()            .unwrap()            .refresh_entries_for_paths(vec![Path::new("i/j").into()])    })    .recv()    .await;     tree.read_with(cx, |tree, _| {        assert_eq!(            tree.descendent_entries(false, false, Path::new("i"))                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            Vec::<PathBuf>::new()        );        assert_eq!(            tree.descendent_entries(false, true, Path::new("i"))                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![Path::new("i/j/k")]        );        assert_eq!(            tree.descendent_entries(true, false, Path::new("i"))                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![Path::new("i"), Path::new("i/l"),]        );    })} #[gpui::test(iterations = 10)]async fn test_circular_symlinks(executor: Arc<Deterministic>, cx: &mut TestAppContext) {    let fs = FakeFs::new(cx.background());    fs.insert_tree(        "/root",        json!({            "lib": {                "a": {                    "a.txt": ""                },                "b": {                    "b.txt": ""                }            }        }),    )    .await;    fs.insert_symlink("/root/lib/a/lib", "..".into()).await;    fs.insert_symlink("/root/lib/b/lib", "..".into()).await;     let tree = Worktree::local(        build_client(cx),        Path::new("/root"),        true,        fs.clone(),        Default::default(),        &mut cx.to_async(),    )    .await    .unwrap();     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())        .await;     tree.read_with(cx, |tree, _| {        assert_eq!(            tree.entries(false)                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![                Path::new(""),                Path::new("lib"),                Path::new("lib/a"),                Path::new("lib/a/a.txt"),                Path::new("lib/a/lib"),                Path::new("lib/b"),                Path::new("lib/b/b.txt"),                Path::new("lib/b/lib"),            ]        );    });     fs.rename(        Path::new("/root/lib/a/lib"),        Path::new("/root/lib/a/lib-2"),        Default::default(),    )    .await    .unwrap();    executor.run_until_parked();    tree.read_with(cx, |tree, _| {        assert_eq!(            tree.entries(false)                .map(|entry| entry.path.as_ref())                .collect::<Vec<_>>(),            vec![                Path::new(""),                Path::new("lib"),                Path::new("lib/a"),                Path::new("lib/a/a.txt"),                Path::new("lib/a/lib-2"),                Path::new("lib/b"),                Path::new("lib/b/b.txt"),                Path::new("lib/b/lib"),            ]        );    });} #[gpui::test]async fn test_symlinks_pointing_outside(cx: &mut TestAppContext) {    let fs = FakeFs::new(cx.background());    fs.insert_tree(        "/root",        json!({            "dir1": {                "deps": {                    // symlinks here                },                "src": {                    "a.rs": "",                    "b.rs": "",                },            },            "dir2": {                "src": {                    "c.rs": "",                    "d.rs": "",                }            },            "dir3": {                "deps": {},                "src": {                    "e.rs": "",                    "f.rs": "",                },            }        }),    )    .await;     // These symlinks point to directories outside of the worktree's root, dir1.    fs.insert_symlink("/root/dir1/deps/dep-dir2", "../../dir2".into())        .await;    fs.insert_symlink("/root/dir1/deps/dep-dir3", "../../dir3".into())        .await;     let tree = Worktree::local(        build_client(cx),        Path::new("/root/dir1"),        true,        fs.clone(),        Default::default(),        &mut cx.to_async(),    )    .await    .unwrap();     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())        .await;     let tree_updates = Arc::new(Mutex::new(Vec::new()));    tree.update(cx, |_, cx| {        let tree_updates = tree_updates.clone();        cx.subscribe(&tree, move |_, _, event, _| {            if let Event::UpdatedEntries(update) = event {                tree_updates.lock().extend(                    update                        .iter()                        .map(|(path, _, change)| (path.clone(), *change)),                );            }        })        .detach();    });     // The symlinked directories are not scanned by default.    tree.read_with(cx, |tree, _| {        assert_eq!(            tree.entries(true)                .map(|entry| (entry.path.as_ref(), entry.is_external))                .collect::<Vec<_>>(),            vec![                (Path::new(""), false),                (Path::new("deps"), false),                (Path::new("deps/dep-dir2"), true),                (Path::new("deps/dep-dir3"), true),                (Path::new("src"), false),                (Path::new("src/a.rs"), false),                (Path::new("src/b.rs"), false),            ]        );         assert_eq!(            tree.entry_for_path("deps/dep-dir2").unwrap().kind,            EntryKind::UnloadedDir        );    });     // Expand one of the symlinked directories.    tree.read_with(cx, |tree, _| {        tree.as_local()            .unwrap()            .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3").into()])    })    .recv()    .await;     // The expanded directory's contents are loaded. Subdirectories are    // not scanned yet.    tree.read_with(cx, |tree, _| {        assert_eq!(            tree.entries(true)                .map(|entry| (entry.path.as_ref(), entry.is_external))                .collect::<Vec<_>>(),            vec![                (Path::new(""), false),                (Path::new("deps"), false),                (Path::new("deps/dep-dir2"), true),                (Path::new("deps/dep-dir3"), true),                (Path::new("deps/dep-dir3/deps"), true),                (Path::new("deps/dep-dir3/src"), true),                (Path::new("src"), false),                (Path::new("src/a.rs"), false),                (Path::new("src/b.rs"), false),            ]        );    });    assert_eq!(        mem::take(&mut *tree_updates.lock()),        &[            (Path::new("deps/dep-dir3").into(), PathChange::Loaded),            (Path::new("deps/dep-dir3/deps").into(), PathChange::Loaded),            (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded)        ]    );     // Expand a subdirectory of one of the symlinked directories.    tree.read_with(cx, |tree, _| {        tree.as_local()            .unwrap()            .refresh_entries_for_paths(vec![Path::new("deps/dep-dir3/src").into()])    })    .recv()    .await;     // The expanded subdirectory's contents are loaded.    tree.read_with(cx, |tree, _| {        assert_eq!(            tree.entries(true)                .map(|entry| (entry.path.as_ref(), entry.is_external))                .collect::<Vec<_>>(),            vec![                (Path::new(""), false),                (Path::new("deps"), false),                (Path::new("deps/dep-dir2"), true),                (Path::new("deps/dep-dir3"), true),                (Path::new("deps/dep-dir3/deps"), true),                (Path::new("deps/dep-dir3/src"), true),                (Path::new("deps/dep-dir3/src/e.rs"), true),                (Path::new("deps/dep-dir3/src/f.rs"), true),                (Path::new("src"), false),                (Path::new("src/a.rs"), false),                (Path::new("src/b.rs"), false),            ]        );    });     assert_eq!(        mem::take(&mut *tree_updates.lock()),        &[            (Path::new("deps/dep-dir3/src").into(), PathChange::Loaded),            (                Path::new("deps/dep-dir3/src/e.rs").into(),                PathChange::Loaded            ),            (                Path::new("deps/dep-dir3/src/f.rs").into(),                PathChange::Loaded            )        ]    );} #[gpui::test]async fn test_open_gitignored_files(cx: &mut TestAppContext) {    let fs = FakeFs::new(cx.background());    fs.insert_tree(        "/root",        json!({            ".gitignore": "node_modules\n",            "one": {                "node_modules": {                    "a": {                        "a1.js": "a1",                        "a2.js": "a2",                    },                    "b": {                        "b1.js": "b1",                        "b2.js": "b2",                    },                    "c": {                        "c1.js": "c1",                        "c2.js": "c2",                    }                },            },            "two": {                "x.js": "",                "y.js": "",            },        }),    )    .await;     let tree = Worktree::local(        build_client(cx),        Path::new("/root"),        true,        fs.clone(),        Default::default(),        &mut cx.to_async(),    )    .await    .unwrap();     cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())        .await;     tree.read_with(cx, |tree, _| {        assert_eq!(            tree.entries(true)                .map(|entry| (entry.path.as_ref(), entry.is_ignored))                .collect::<Vec<_>>(),            vec![                (Path::new(""), false),                (Path::new(".gitignore"), false),                (Path::new("one"), false),                (Path::new("one/node_modules"), true),                (Path::new("two"), false),                (Path::new("two/x.js"), false),                (Path::new("two/y.js"), false),            ]        );    });

Lines 1–500 of 2141.