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}