fs.rs

  1use std::{
  2    io::Write,
  3    path::{Path, PathBuf},
  4};
  5
  6use fs::*;
  7use gpui::BackgroundExecutor;
  8use serde_json::json;
  9use tempfile::TempDir;
 10use util::path;
 11
 12#[gpui::test]
 13async fn test_fake_fs(executor: BackgroundExecutor) {
 14    let fs = FakeFs::new(executor.clone());
 15    fs.insert_tree(
 16        path!("/root"),
 17        json!({
 18            "dir1": {
 19                "a": "A",
 20                "b": "B"
 21            },
 22            "dir2": {
 23                "c": "C",
 24                "dir3": {
 25                    "d": "D"
 26                }
 27            }
 28        }),
 29    )
 30    .await;
 31
 32    assert_eq!(
 33        fs.files(),
 34        vec![
 35            PathBuf::from(path!("/root/dir1/a")),
 36            PathBuf::from(path!("/root/dir1/b")),
 37            PathBuf::from(path!("/root/dir2/c")),
 38            PathBuf::from(path!("/root/dir2/dir3/d")),
 39        ]
 40    );
 41
 42    fs.create_symlink(path!("/root/dir2/link-to-dir3").as_ref(), "./dir3".into())
 43        .await
 44        .unwrap();
 45
 46    assert_eq!(
 47        fs.canonicalize(path!("/root/dir2/link-to-dir3").as_ref())
 48            .await
 49            .unwrap(),
 50        PathBuf::from(path!("/root/dir2/dir3")),
 51    );
 52    assert_eq!(
 53        fs.canonicalize(path!("/root/dir2/link-to-dir3/d").as_ref())
 54            .await
 55            .unwrap(),
 56        PathBuf::from(path!("/root/dir2/dir3/d")),
 57    );
 58    assert_eq!(
 59        fs.load(path!("/root/dir2/link-to-dir3/d").as_ref())
 60            .await
 61            .unwrap(),
 62        "D",
 63    );
 64}
 65
 66#[gpui::test]
 67async fn test_copy_recursive_with_single_file(executor: BackgroundExecutor) {
 68    let fs = FakeFs::new(executor.clone());
 69    fs.insert_tree(
 70        path!("/outer"),
 71        json!({
 72            "a": "A",
 73            "b": "B",
 74            "inner": {}
 75        }),
 76    )
 77    .await;
 78
 79    assert_eq!(
 80        fs.files(),
 81        vec![
 82            PathBuf::from(path!("/outer/a")),
 83            PathBuf::from(path!("/outer/b")),
 84        ]
 85    );
 86
 87    let source = Path::new(path!("/outer/a"));
 88    let target = Path::new(path!("/outer/a copy"));
 89    copy_recursive(fs.as_ref(), source, target, Default::default())
 90        .await
 91        .unwrap();
 92
 93    assert_eq!(
 94        fs.files(),
 95        vec![
 96            PathBuf::from(path!("/outer/a")),
 97            PathBuf::from(path!("/outer/a copy")),
 98            PathBuf::from(path!("/outer/b")),
 99        ]
100    );
101
102    let source = Path::new(path!("/outer/a"));
103    let target = Path::new(path!("/outer/inner/a copy"));
104    copy_recursive(fs.as_ref(), source, target, Default::default())
105        .await
106        .unwrap();
107
108    assert_eq!(
109        fs.files(),
110        vec![
111            PathBuf::from(path!("/outer/a")),
112            PathBuf::from(path!("/outer/a copy")),
113            PathBuf::from(path!("/outer/b")),
114            PathBuf::from(path!("/outer/inner/a copy")),
115        ]
116    );
117}
118
119#[gpui::test]
120async fn test_copy_recursive_with_single_dir(executor: BackgroundExecutor) {
121    let fs = FakeFs::new(executor.clone());
122    fs.insert_tree(
123        path!("/outer"),
124        json!({
125            "a": "A",
126            "empty": {},
127            "non-empty": {
128                "b": "B",
129            }
130        }),
131    )
132    .await;
133
134    assert_eq!(
135        fs.files(),
136        vec![
137            PathBuf::from(path!("/outer/a")),
138            PathBuf::from(path!("/outer/non-empty/b")),
139        ]
140    );
141    assert_eq!(
142        fs.directories(false),
143        vec![
144            PathBuf::from(path!("/")),
145            PathBuf::from(path!("/outer")),
146            PathBuf::from(path!("/outer/empty")),
147            PathBuf::from(path!("/outer/non-empty")),
148        ]
149    );
150
151    let source = Path::new(path!("/outer/empty"));
152    let target = Path::new(path!("/outer/empty copy"));
153    copy_recursive(fs.as_ref(), source, target, Default::default())
154        .await
155        .unwrap();
156
157    assert_eq!(
158        fs.files(),
159        vec![
160            PathBuf::from(path!("/outer/a")),
161            PathBuf::from(path!("/outer/non-empty/b")),
162        ]
163    );
164    assert_eq!(
165        fs.directories(false),
166        vec![
167            PathBuf::from(path!("/")),
168            PathBuf::from(path!("/outer")),
169            PathBuf::from(path!("/outer/empty")),
170            PathBuf::from(path!("/outer/empty copy")),
171            PathBuf::from(path!("/outer/non-empty")),
172        ]
173    );
174
175    let source = Path::new(path!("/outer/non-empty"));
176    let target = Path::new(path!("/outer/non-empty copy"));
177    copy_recursive(fs.as_ref(), source, target, Default::default())
178        .await
179        .unwrap();
180
181    assert_eq!(
182        fs.files(),
183        vec![
184            PathBuf::from(path!("/outer/a")),
185            PathBuf::from(path!("/outer/non-empty/b")),
186            PathBuf::from(path!("/outer/non-empty copy/b")),
187        ]
188    );
189    assert_eq!(
190        fs.directories(false),
191        vec![
192            PathBuf::from(path!("/")),
193            PathBuf::from(path!("/outer")),
194            PathBuf::from(path!("/outer/empty")),
195            PathBuf::from(path!("/outer/empty copy")),
196            PathBuf::from(path!("/outer/non-empty")),
197            PathBuf::from(path!("/outer/non-empty copy")),
198        ]
199    );
200}
201
202#[gpui::test]
203async fn test_copy_recursive(executor: BackgroundExecutor) {
204    let fs = FakeFs::new(executor.clone());
205    fs.insert_tree(
206        path!("/outer"),
207        json!({
208            "inner1": {
209                "a": "A",
210                "b": "B",
211                "inner3": {
212                    "d": "D",
213                },
214                "inner4": {}
215            },
216            "inner2": {
217                "c": "C",
218            }
219        }),
220    )
221    .await;
222
223    assert_eq!(
224        fs.files(),
225        vec![
226            PathBuf::from(path!("/outer/inner1/a")),
227            PathBuf::from(path!("/outer/inner1/b")),
228            PathBuf::from(path!("/outer/inner2/c")),
229            PathBuf::from(path!("/outer/inner1/inner3/d")),
230        ]
231    );
232    assert_eq!(
233        fs.directories(false),
234        vec![
235            PathBuf::from(path!("/")),
236            PathBuf::from(path!("/outer")),
237            PathBuf::from(path!("/outer/inner1")),
238            PathBuf::from(path!("/outer/inner2")),
239            PathBuf::from(path!("/outer/inner1/inner3")),
240            PathBuf::from(path!("/outer/inner1/inner4")),
241        ]
242    );
243
244    let source = Path::new(path!("/outer"));
245    let target = Path::new(path!("/outer/inner1/outer"));
246    copy_recursive(fs.as_ref(), source, target, Default::default())
247        .await
248        .unwrap();
249
250    assert_eq!(
251        fs.files(),
252        vec![
253            PathBuf::from(path!("/outer/inner1/a")),
254            PathBuf::from(path!("/outer/inner1/b")),
255            PathBuf::from(path!("/outer/inner2/c")),
256            PathBuf::from(path!("/outer/inner1/inner3/d")),
257            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
258            PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
259            PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
260            PathBuf::from(path!("/outer/inner1/outer/inner1/inner3/d")),
261        ]
262    );
263    assert_eq!(
264        fs.directories(false),
265        vec![
266            PathBuf::from(path!("/")),
267            PathBuf::from(path!("/outer")),
268            PathBuf::from(path!("/outer/inner1")),
269            PathBuf::from(path!("/outer/inner2")),
270            PathBuf::from(path!("/outer/inner1/inner3")),
271            PathBuf::from(path!("/outer/inner1/inner4")),
272            PathBuf::from(path!("/outer/inner1/outer")),
273            PathBuf::from(path!("/outer/inner1/outer/inner1")),
274            PathBuf::from(path!("/outer/inner1/outer/inner2")),
275            PathBuf::from(path!("/outer/inner1/outer/inner1/inner3")),
276            PathBuf::from(path!("/outer/inner1/outer/inner1/inner4")),
277        ]
278    );
279}
280
281#[gpui::test]
282async fn test_copy_recursive_with_overwriting(executor: BackgroundExecutor) {
283    let fs = FakeFs::new(executor.clone());
284    fs.insert_tree(
285        path!("/outer"),
286        json!({
287            "inner1": {
288                "a": "A",
289                "b": "B",
290                "outer": {
291                    "inner1": {
292                        "a": "B"
293                    }
294                }
295            },
296            "inner2": {
297                "c": "C",
298            }
299        }),
300    )
301    .await;
302
303    assert_eq!(
304        fs.files(),
305        vec![
306            PathBuf::from(path!("/outer/inner1/a")),
307            PathBuf::from(path!("/outer/inner1/b")),
308            PathBuf::from(path!("/outer/inner2/c")),
309            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
310        ]
311    );
312    assert_eq!(
313        fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
314            .await
315            .unwrap(),
316        "B",
317    );
318
319    let source = Path::new(path!("/outer"));
320    let target = Path::new(path!("/outer/inner1/outer"));
321    copy_recursive(
322        fs.as_ref(),
323        source,
324        target,
325        CopyOptions {
326            overwrite: true,
327            ..Default::default()
328        },
329    )
330    .await
331    .unwrap();
332
333    assert_eq!(
334        fs.files(),
335        vec![
336            PathBuf::from(path!("/outer/inner1/a")),
337            PathBuf::from(path!("/outer/inner1/b")),
338            PathBuf::from(path!("/outer/inner2/c")),
339            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
340            PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
341            PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
342            PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
343        ]
344    );
345    assert_eq!(
346        fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
347            .await
348            .unwrap(),
349        "A"
350    );
351}
352
353#[gpui::test]
354async fn test_copy_recursive_with_ignoring(executor: BackgroundExecutor) {
355    let fs = FakeFs::new(executor.clone());
356    fs.insert_tree(
357        path!("/outer"),
358        json!({
359            "inner1": {
360                "a": "A",
361                "b": "B",
362                "outer": {
363                    "inner1": {
364                        "a": "B"
365                    }
366                }
367            },
368            "inner2": {
369                "c": "C",
370            }
371        }),
372    )
373    .await;
374
375    assert_eq!(
376        fs.files(),
377        vec![
378            PathBuf::from(path!("/outer/inner1/a")),
379            PathBuf::from(path!("/outer/inner1/b")),
380            PathBuf::from(path!("/outer/inner2/c")),
381            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
382        ]
383    );
384    assert_eq!(
385        fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
386            .await
387            .unwrap(),
388        "B",
389    );
390
391    let source = Path::new(path!("/outer"));
392    let target = Path::new(path!("/outer/inner1/outer"));
393    copy_recursive(
394        fs.as_ref(),
395        source,
396        target,
397        CopyOptions {
398            ignore_if_exists: true,
399            ..Default::default()
400        },
401    )
402    .await
403    .unwrap();
404
405    assert_eq!(
406        fs.files(),
407        vec![
408            PathBuf::from(path!("/outer/inner1/a")),
409            PathBuf::from(path!("/outer/inner1/b")),
410            PathBuf::from(path!("/outer/inner2/c")),
411            PathBuf::from(path!("/outer/inner1/outer/inner1/a")),
412            PathBuf::from(path!("/outer/inner1/outer/inner1/b")),
413            PathBuf::from(path!("/outer/inner1/outer/inner2/c")),
414            PathBuf::from(path!("/outer/inner1/outer/inner1/outer/inner1/a")),
415        ]
416    );
417    assert_eq!(
418        fs.load(path!("/outer/inner1/outer/inner1/a").as_ref())
419            .await
420            .unwrap(),
421        "B"
422    );
423}
424
425#[gpui::test]
426async fn test_realfs_atomic_write(executor: BackgroundExecutor) {
427    // With the file handle still open, the file should be replaced
428    // https://github.com/zed-industries/zed/issues/30054
429    let fs = RealFs::new(None, executor);
430    let temp_dir = TempDir::new().unwrap();
431    let file_to_be_replaced = temp_dir.path().join("file.txt");
432    let mut file = std::fs::File::create_new(&file_to_be_replaced).unwrap();
433    file.write_all(b"Hello").unwrap();
434    // drop(file);  // We still hold the file handle here
435    let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
436    assert_eq!(content, "Hello");
437    smol::block_on(fs.atomic_write(file_to_be_replaced.clone(), "World".into())).unwrap();
438    let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
439    assert_eq!(content, "World");
440}
441
442#[gpui::test]
443async fn test_realfs_atomic_write_non_existing_file(executor: BackgroundExecutor) {
444    let fs = RealFs::new(None, executor);
445    let temp_dir = TempDir::new().unwrap();
446    let file_to_be_replaced = temp_dir.path().join("file.txt");
447    smol::block_on(fs.atomic_write(file_to_be_replaced.clone(), "Hello".into())).unwrap();
448    let content = std::fs::read_to_string(&file_to_be_replaced).unwrap();
449    assert_eq!(content, "Hello");
450}
451
452#[gpui::test]
453#[cfg(target_os = "windows")]
454async fn test_realfs_canonicalize(executor: BackgroundExecutor) {
455    use util::paths::SanitizedPath;
456
457    let fs = RealFs::new(None, executor);
458    let temp_dir = TempDir::new().unwrap();
459    let file = temp_dir.path().join("test (1).txt");
460    let file = SanitizedPath::new(&file);
461    std::fs::write(&file, "test").unwrap();
462
463    let canonicalized = fs.canonicalize(file.as_path()).await;
464    assert!(canonicalized.is_ok());
465}
466
467#[gpui::test]
468async fn test_rename(executor: BackgroundExecutor) {
469    let fs = FakeFs::new(executor.clone());
470    fs.insert_tree(
471        path!("/root"),
472        json!({
473            "src": {
474                "file_a.txt": "content a",
475                "file_b.txt": "content b"
476            }
477        }),
478    )
479    .await;
480
481    fs.rename(
482        Path::new(path!("/root/src/file_a.txt")),
483        Path::new(path!("/root/src/new/renamed_a.txt")),
484        RenameOptions {
485            create_parents: true,
486            ..Default::default()
487        },
488    )
489    .await
490    .unwrap();
491
492    // Assert that the `file_a.txt` file was being renamed and moved to a
493    // different directory that did not exist before.
494    assert_eq!(
495        fs.files(),
496        vec![
497            PathBuf::from(path!("/root/src/file_b.txt")),
498            PathBuf::from(path!("/root/src/new/renamed_a.txt")),
499        ]
500    );
501
502    let result = fs
503        .rename(
504            Path::new(path!("/root/src/file_b.txt")),
505            Path::new(path!("/root/src/old/renamed_b.txt")),
506            RenameOptions {
507                create_parents: false,
508                ..Default::default()
509            },
510        )
511        .await;
512
513    // Assert that the `file_b.txt` file was not renamed nor moved, as
514    // `create_parents` was set to `false`.
515    // different directory that did not exist before.
516    assert!(result.is_err());
517    assert_eq!(
518        fs.files(),
519        vec![
520            PathBuf::from(path!("/root/src/file_b.txt")),
521            PathBuf::from(path!("/root/src/new/renamed_a.txt")),
522        ]
523    );
524}
525
526#[gpui::test]
527#[cfg(unix)]
528async fn test_realfs_broken_symlink_metadata(executor: BackgroundExecutor) {
529    let tempdir = TempDir::new().unwrap();
530    let path = tempdir.path();
531    let fs = RealFs::new(None, executor);
532    let symlink_path = path.join("symlink");
533    smol::block_on(fs.create_symlink(&symlink_path, PathBuf::from("file_a.txt"))).unwrap();
534    let metadata = fs
535        .metadata(&symlink_path)
536        .await
537        .expect("metadata call succeeds")
538        .expect("metadata returned");
539    assert!(metadata.is_symlink);
540    assert!(!metadata.is_dir);
541    assert!(!metadata.is_fifo);
542    assert!(!metadata.is_executable);
543    // don't care about len or mtime on symlinks?
544}
545
546#[gpui::test]
547#[cfg(unix)]
548async fn test_realfs_symlink_loop_metadata(executor: BackgroundExecutor) {
549    let tempdir = TempDir::new().unwrap();
550    let path = tempdir.path();
551    let fs = RealFs::new(None, executor);
552    let symlink_path = path.join("symlink");
553    smol::block_on(fs.create_symlink(&symlink_path, PathBuf::from("symlink"))).unwrap();
554    let metadata = fs
555        .metadata(&symlink_path)
556        .await
557        .expect("metadata call succeeds")
558        .expect("metadata returned");
559    assert!(metadata.is_symlink);
560    assert!(!metadata.is_dir);
561    assert!(!metadata.is_fifo);
562    assert!(!metadata.is_executable);
563    // don't care about len or mtime on symlinks?
564}