Add integration test to exercise formatting via external command

Antonio Scandurra created

Change summary

crates/collab/src/integration_tests.rs | 34 +++++++++++++++++++++++----
crates/project/src/fs.rs               | 26 +-------------------
crates/zed/src/zed.rs                  | 33 ++++++++++++++++++---------
3 files changed, 53 insertions(+), 40 deletions(-)

Detailed changes

crates/collab/src/integration_tests.rs 🔗

@@ -35,7 +35,7 @@ use project::{
 use rand::prelude::*;
 use rpc::PeerId;
 use serde_json::json;
-use settings::Settings;
+use settings::{FormatOnSave, Settings};
 use sqlx::types::time::OffsetDateTime;
 use std::{
     cell::RefCell,
@@ -1912,7 +1912,6 @@ async fn test_reloading_buffer_manually(cx_a: &mut TestAppContext, cx_b: &mut Te
 
 #[gpui::test(iterations = 10)]
 async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
-    cx_a.foreground().forbid_parking();
     let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -1932,11 +1931,15 @@ async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppCon
     let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default());
     client_a.language_registry.add(Arc::new(language));
 
+    // Here we insert a fake tree with a directory that exists on disk. This is needed
+    // because later we'll invoke a command, which requires passing a working directory
+    // that points to a valid location on disk.
+    let directory = env::current_dir().unwrap();
     client_a
         .fs
-        .insert_tree("/a", json!({ "a.rs": "let one = two" }))
+        .insert_tree(&directory, json!({ "a.rs": "let one = \"two\"" }))
         .await;
-    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    let (project_a, worktree_id) = client_a.build_local_project(&directory, cx_a).await;
     let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
 
     let buffer_b = cx_b
@@ -1967,7 +1970,28 @@ async fn test_formatting_buffer(cx_a: &mut TestAppContext, cx_b: &mut TestAppCon
         .unwrap();
     assert_eq!(
         buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
-        "let honey = two"
+        "let honey = \"two\""
+    );
+
+    // Ensure buffer can be formatted using an external command. Notice how the
+    // host's configuration is honored as opposed to using the guest's settings.
+    cx_a.update(|cx| {
+        cx.update_global(|settings: &mut Settings, _| {
+            settings.language_settings.format_on_save = Some(FormatOnSave::External {
+                command: "awk".to_string(),
+                arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
+            });
+        });
+    });
+    project_b
+        .update(cx_b, |project, cx| {
+            project.format(HashSet::from_iter([buffer_b.clone()]), true, cx)
+        })
+        .await
+        .unwrap();
+    assert_eq!(
+        buffer_b.read_with(cx_b, |buffer, _| buffer.text()),
+        format!("let honey = \"{}/a.rs\"\n", directory.to_str().unwrap())
     );
 }
 

crates/project/src/fs.rs 🔗

@@ -334,28 +334,6 @@ impl FakeFs {
         })
     }
 
-    pub async fn insert_dir(&self, path: impl AsRef<Path>) {
-        let mut state = self.state.lock().await;
-        let path = path.as_ref();
-        state.validate_path(path).unwrap();
-
-        let inode = state.next_inode;
-        state.next_inode += 1;
-        state.entries.insert(
-            path.to_path_buf(),
-            FakeFsEntry {
-                metadata: Metadata {
-                    inode,
-                    mtime: SystemTime::now(),
-                    is_dir: true,
-                    is_symlink: false,
-                },
-                content: None,
-            },
-        );
-        state.emit_event(&[path]).await;
-    }
-
     pub async fn insert_file(&self, path: impl AsRef<Path>, content: String) {
         let mut state = self.state.lock().await;
         let path = path.as_ref();
@@ -392,7 +370,7 @@ impl FakeFs {
 
             match tree {
                 Object(map) => {
-                    self.insert_dir(path).await;
+                    self.create_dir(path).await.unwrap();
                     for (name, contents) in map {
                         let mut path = PathBuf::from(path);
                         path.push(name);
@@ -400,7 +378,7 @@ impl FakeFs {
                     }
                 }
                 Null => {
-                    self.insert_dir(&path).await;
+                    self.create_dir(&path).await.unwrap();
                 }
                 String(contents) => {
                     self.insert_file(&path, contents).await;

crates/zed/src/zed.rs 🔗

@@ -554,7 +554,7 @@ mod tests {
         });
 
         let save_task = workspace.update(cx, |workspace, cx| workspace.save_active_item(false, cx));
-        app_state.fs.as_fake().insert_dir("/root").await;
+        app_state.fs.create_dir(Path::new("/root")).await.unwrap();
         cx.simulate_new_path_selection(|_| Some(PathBuf::from("/root/the-new-name")));
         save_task.await.unwrap();
         editor.read_with(cx, |editor, cx| {
@@ -680,14 +680,25 @@ mod tests {
     async fn test_open_paths(cx: &mut TestAppContext) {
         let app_state = init(cx);
 
-        let fs = app_state.fs.as_fake();
-        fs.insert_dir("/dir1").await;
-        fs.insert_dir("/dir2").await;
-        fs.insert_dir("/dir3").await;
-        fs.insert_file("/dir1/a.txt", "".into()).await;
-        fs.insert_file("/dir2/b.txt", "".into()).await;
-        fs.insert_file("/dir3/c.txt", "".into()).await;
-        fs.insert_file("/d.txt", "".into()).await;
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree(
+                "/",
+                json!({
+                    "dir1": {
+                        "a.txt": ""
+                    },
+                    "dir2": {
+                        "b.txt": ""
+                    },
+                    "dir3": {
+                        "c.txt": ""
+                    },
+                    "d.txt": ""
+                }),
+            )
+            .await;
 
         let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx));
@@ -891,7 +902,7 @@ mod tests {
     #[gpui::test]
     async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
         let app_state = init(cx);
-        app_state.fs.as_fake().insert_dir("/root").await;
+        app_state.fs.create_dir(Path::new("/root")).await.unwrap();
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         project.update(cx, |project, _| project.languages().add(rust_lang()));
@@ -980,7 +991,7 @@ mod tests {
     #[gpui::test]
     async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) {
         let app_state = init(cx);
-        app_state.fs.as_fake().insert_dir("/root").await;
+        app_state.fs.create_dir(Path::new("/root")).await.unwrap();
 
         let project = Project::test(app_state.fs.clone(), [], cx).await;
         project.update(cx, |project, _| project.languages().add(rust_lang()));